const qiniu = require("qiniu");
const stream = require('stream');
const getEtag = require("./qetag.js");

let Uploader = function(options) {
    if (!options) {
        options = {};
    }
    if (!options.accessKey || !options.secretKey) {
        throw new Error("密钥为空")
    }
    if (!options.bucket) {
        throw new Error("存储空间名为空")
    }
    if (!options.domain) {
        throw new Error("域名为空")
    }
    //一般为固定密钥
    this.accessKey = options.accessKey;
    this.secretKey = options.secretKey;

    //目标存储空间名
    this.bucket = options.bucket;

    //目标存储空间绑定的域名
    this.domain = options.domain;

    //鉴权对象
    this.mac = new qiniu.auth.digest.Mac(this.accessKey, this.secretKey);

    //配置对象
    var config = new qiniu.conf.Config();
    config.zone = qiniu.zone.Zone_z0;
    this.config = config;

    //资源管理对象
    this.bucketManager = new qiniu.rs.BucketManager(this.mac, config);

    //CDN相关功能
    this.cdnManager = new qiniu.cdn.CdnManager(this.mac);
};

//key:目标文件在存储空间的路径
Uploader.prototype.getUploadToken = function(key) {
    let options = {
        //增加Key代表该凭证可以覆盖上传
        scope: this.bucket + (key ? ":" + key : ""),

        //上传凭证的有效期，默认3600，小图片可以无视，主要是断点续传
        expires: 3600,

        //上传回复的凭证
        returnBody: '{"key":"$(key)","hash":"$(etag)","size":"${fsize}"}'
    };
    let putPolicy = new qiniu.rs.PutPolicy(options);
    let uploadToken = putPolicy.uploadToken(this.mac);

    return uploadToken;
};

//上传前的准备工作
//file:目标文件在本地的路径
//opts:内容参考代码中options
Uploader.prototype.beforeUpload = function(file, opts) {
    let uploader = this;
    let options = @.merge.advanced({
        "!defaultValue": {},
        //是否添加uuid以和同名文件进行区分，不区分则自动覆盖上传
        "update": {
            "!defaultValue": false,
            "!valueType": "boolean"
        },
        //指定目标路径，在字节数组和数据流上传时必须
        "path": {
            "!defaultValue": "",
            "!valueType": "string"
        },
        //字节数组上传时的文本
        "content": {
            "!defaultValue": "",
            "!valueType": "string"
        },
        //数据流上传使用的可读的流
        "stream": {
            "!valueType": "object"
        },
        //是否启用覆盖上传的凭证，在字节数组和数据流上传时需要判断
        "needCover": {
            "!defaultValue": false,
            "!valueType": "boolean"
        },
        //字符串上传和数据流上传可以不用文件
        "needFile": {
            "!defaultValue": false,
            "!valueType": "boolean"
        }
    }, opts);

    return @.async(function() {
        if (!options.needFile || (file && @.fs.exists.file(file))) {

            let key;

            if (options.update) {
                key = options.path || (@.fs.basename(file) + @.fs.extname(file));
            } else {
                key = options.path || ((@.uuid() + "/" + @.fs.basename(file) + @.fs.extname(file)));
            }
            if (key[0] == "/") {
                key = key.substr(1);
            }

            this.next(key);
        } else {
            this.reject("target path : " + file + " not exists or not a file");
        }
    });
};

//自动判断文件大小进行表单或是分片上传
Uploader.prototype.autoUpload = function(file, opts) {
    let uploader = this;

    return @.async(function() {
        if (file && @.fs.exists.file(file)) {
            /* 获取目标文件状态，主要是获取文件大小
            {
                "type": type, //file,dir,link,socket,device.block,device.character,fifo,unknown
                "mode": stats.mode,
                "node": stats.node,
                "size": stats.size,
                "accessTime": stats.atime,
                "modifyTime": stats.mtime,
                "changeTime": stats.ctime,
                "createTime": new Date(Math.min(stats.atime.getTime(), stats.mtime.getTime(), stats.ctime.getTime(), stats.birthtime.getTime())),
                "rawStats": stats
            } */

            this.next(@.fs.fileStats(file));
        } else {
            this.reject("target path : " + file + " not exists or not a file");
        }
    }).then(function(fileStats) {
        if (fileStats.size > 10 * 1024 * 1024) {
            uploader.resumeUpload(file, opts).pipe(this);
        } else {
            uploader.formUpload(file, opts).pipe(this);
        }
    });
};

//表单上传
Uploader.prototype.formUpload = function(file, opts) {
    let uploader = this;
    let formUploader = new qiniu.form_up.FormUploader(uploader.config);
    let putExtra = new qiniu.form_up.PutExtra();
    opts.needFile = true

    return uploader.beforeUpload(file, opts).then(function(key) {
        let uploadToken = uploader.getUploadToken(key);
        formUploader.putFile(uploadToken, key, file, putExtra, this.test);
    }).then(function(result) {
        if (result.error) {
            this.reject(result.error)
        } else {
            this.next(uploader.getUrlFromPublicBucket(uploader.domain, result.key), {
                "hash": result.hash,
                "key": result.key,
                "size": result.size
            });
        }
    });
};

//字节数组上传
//opts.path
//opts.content
//opts.needCover
Uploader.prototype.stringUpload = function(opts) {
    if (!opts.path) {
        throw new Error("opts.path is empty");
    }
    if (!opts.content) {
        throw new Error("opts.content is empty");
    }
    let uploader = this;
    let formUploader = new qiniu.form_up.FormUploader(uploader.config);
    let putExtra = new qiniu.form_up.PutExtra();

    return uploader.beforeUpload(null, opts).then(function(key) {
        let uploadToken = opts.needCover ? uploader.getUploadToken(key) : uploader.getUploadToken();
        formUploader.put(uploadToken, key, opts.content, putExtra, this.test);
    }).then(function(result) {
        if (result.error) {
            this.reject(result.error)
        } else {
            this.next(uploader.getUrlFromPublicBucket(uploader.domain, result.key), {
                "hash": result.hash,
                "key": result.key,
                "size": result.size
            });
        }
    });
};

//数据流上传
//opts.path
//opts.stream
//opts.needCover
Uploader.prototype.streamUpload = function(opts) {
    if (!opts.path) {
        throw new Error("opts.path is empty");
    }
    if (!opts.stream) {
        throw new Error("opts.stream is empty");
    }
    let uploader = this;
    let formUploader = new qiniu.form_up.FormUploader(uploader.config);
    let putExtra = new qiniu.form_up.PutExtra();

    return uploader.beforeUpload(null, opts).then(function(key) {
        let uploadToken = opts.needCover ? uploader.getUploadToken(key) : uploader.getUploadToken();
        formUploader.putStream(uploadToken, key, opts.stream, putExtra, this.test);
    }).then(function(result) {
        if (result.error) {
            this.reject(result.error)
        } else {
            this.next(uploader.getUrlFromPublicBucket(uploader.domain, result.key), {
                "hash": result.hash,
                "key": result.key,
                "size": result.size
            });
        }
    });
};

//Buffer上传
//opts.path
//opts.buffer
//opts.needCover
Uploader.prototype.bufferUpload = function(opts) {
    if (!opts.path) {
        throw new Error("opts.path is empty");
    }
    if (!opts.buffer) {
        throw new Error("opts.buffer is empty");
    }
    let uploader = this;
    let bufferStream = new stream.PassThrough();
    bufferStream.end(opts.buffer);
    opts.stream = bufferStream

    return uploader.streamUpload(opts)
};

//分片上传
Uploader.prototype.resumeUpload = function(file, opts) {
    let uploader = this;
    let formUploader = new qiniu.resume_up.ResumeUploader(uploader.config);
    let putExtra = new qiniu.resume_up.PutExtra();
    opts.needFile = true

    return uploader.beforeUpload(file, opts).then(function(key) {
        let uploadToken = uploader.getUploadToken(key);
        // 如果指定了断点记录文件，那么下次会从指定的该文件尝试读取上次上传的进度，以实现断点续传
        let fileName = key
        fileName = @.hash.md5(fileName.replace(/\//g, "-"))
        fileName += ".log"
        if (!@.fs.exists.dir(@.fs.tempPath("resume"))) {
            @.fs.makeDirs(@.fs.tempPath("resume"));
        }
        let logFile = @.fs.resolvePath(@.fs.tempPath("resume"), fileName);
        if (!@.fs.exists.file(logFile)) {
            @.fs.writeFile.sync(logFile, "");
        }
        putExtra.resumeRecordFile = logFile;
        formUploader.putFile(uploadToken, key, file, putExtra, this.test);
    }).then(function(result) {
        if (result.error) {
            this.reject(result.error)
        } else {
            this.next(uploader.getUrlFromPublicBucket(uploader.domain, result.key), {
                "hash": result.hash,
                "key": result.key,
                "size": result.size
            });
        }
    })
};

//获取公共空间的外链
Uploader.prototype.getUrlFromPublicBucket = function(domain, key) {
    if (!domain) {
        domain = this.domain
    }
    return this.bucketManager.publicDownloadUrl(domain, key);
};

//获取私有空间的外链
Uploader.prototype.getUrlFromPrivateBucket = function(domain, key, expires) {
    let deadline = parseInt(Date.now() / 1000) + parseInt(expires || 3600); //默认一小时过期

    return this.bucketManager.privateDownloadUrl(domain, key, deadline);
};

Uploader.prototype.stat = function(url, key) {
    if (!url && !key) {
        throw new Error("url and key empty");
    }
    let uploader = this;

    key = key || url.replace(uploader.domain + "/", "");
    url = url || uploader.domain + "/" + key

    return @.async(function() {
        uploader.bucketManager.stat(uploader.bucket, key, this.test);
    }).then(function(result) {
        this.next({
            exists: !result.error,
            url: url,
            size: result.fsize,
            hash: result.hash,
            mime_type: result.mimeType,
            put_time: result.put_time
        });
    });
};

Uploader.prototype.fetch = function(resourceUrl, key) {
    let uploader = this;

    key = key || @.uuid() + "-" + resourceUrl.split("/").pop();

    return @.async(function() {
        uploader.bucketManager.fetch(resourceUrl, uploader.bucket, key, this.test);
    });
};

Uploader.prototype.refreshUrls = function(urlsToRefresh) {
    let uploader = this;
    return @.async(function() {
        uploader.cdnManager.refreshUrls(urlsToRefresh, this.test);
    }).then(function(resultString) {
        let result = JSON.parse(resultString)
        if (result.code === 200) {
            this.next(result)
        } else {
            this.reject(result.error)
        }
    });
};

Uploader.prototype.getEtag = function(buffer) {
    return @.async(function() {
        getEtag(buffer, this.next)
    })
}

// @param options 列举操作的可选参数
//                prefix    列举的文件前缀
//                marker    上一次列举返回的位置标记，作为本次列举的起点信息
//                limit     每次返回的最大列举文件数量
//                delimiter 指定目录分隔符
Uploader.prototype.listPrefix = function(options) {
    let uploader = this;
    return @.async(function() {
        uploader.bucketManager.listPrefix(uploader.bucket, options, (error, respBody, respInfo) => {
            if (error) {
                this.reject(error)
            } else {
                this.next({ respBody, respInfo })
            }
        });
    })
}

Uploader.prototype.deleteBatch = function(deleteFiles) {
    let uploader = this;
    return @.async(function() {
        let deleteOperations = deleteFiles.map(file => qiniu.rs.deleteOp(uploader.bucket, file))
        uploader.bucketManager.batch(deleteOperations, (error, respBody, respInfo) => {
            if (error) {
                this.reject(error)
            } else {
                // 200 is success, 298 is part success
                if (parseInt(respInfo.statusCode / 100) == 2) {
                    respBody.forEach(function(item) {
                        if (item.code == 200) {
                            @dump(item.code + "\tsuccess")
                        } else {
                            @dump(item.code + "\t" + item.data.error);
                        }
                    });
                    this.next({ respBody, respInfo });
                } else {
                    this.next({ respBody, respInfo });
                }
            }
        });
    })
}

module.exports = Uploader;